﻿using Devil;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;

namespace GameToolkit.Editor
{
    using AnimCtrl = AnimatorController;
    using AnimSM = AnimatorStateMachine;
    using AnimLayer = AnimatorControllerLayer;
    using AnimBlend = BlendTree;
    using AnimState = AnimatorState;

    public class CreateAnimatorWizzard : EditorWindow
    {
        enum EAction
        {
            CreateClip,
            RepathClip
        }

        [MenuItem("Assets/Utils/Add (Delete) Animation Clips", true)]
        [MenuItem("Assets/Utils/Repath Animation Clips", true)]
        static bool ValidateOpenCreater()
        {
            return Selection.activeObject is AnimCtrl;
        }

        [MenuItem("Assets/Utils/Add (Delete) Animation Clips")]
        static void OpenCreater()
        {
            //OpenCreater(Selection.activeObject); 
            var path = AssetDatabase.GetAssetPath(Selection.activeObject);
            if (string.IsNullOrEmpty(path))
                return;
            OpenCreater(path);
        }

        [MenuItem("Assets/Utils/Repath Animation Clips")]
        static void RepathAnimationClips()
        {
            var path = AssetDatabase.GetAssetPath(Selection.activeObject);
            if (string.IsNullOrEmpty(path))
                return;
            OpenRepath(path);
        }

        static bool GetSelectedMachine(out AnimatorState baseState, out AnimatorStateMachine machine, out AnimatorController controller)
        {
            var selected = Selection.objects;
            baseState = null;
            machine = null;
            controller = null;
            if (selected != null && selected.Length == 2)
            {
                for (int i = 0; i < selected.Length; i++)
                {
                    var sel = selected[i];
                    if (sel is AnimatorState)
                        baseState = (AnimatorState)selected[i];
                    else if (sel is AnimatorStateMachine)
                        machine = (AnimatorStateMachine)sel;
                    else if (sel is AnimatorController)
                        controller = (AnimatorController)sel;
                }
            }
            return controller != null && (baseState != null || machine != null);
        }

        static bool FindState(AnimatorStateMachine machine, Object stateOrMachine, ref AnimatorStateMachine parentMachine, ref Vector3 pos)
        {
            var state = stateOrMachine as AnimatorState;
            if (state != null)
            {
                foreach (var st in machine.states)
                {
                    if (st.state == state)
                    {
                        pos = st.position;
                        parentMachine = machine;
                        return true;
                    }
                }
            }
            var m = stateOrMachine as AnimatorStateMachine;
            foreach (var sub in machine.stateMachines)
            {
                if (m != null && sub.stateMachine == m)
                {
                    pos = sub.position;
                    parentMachine = machine;
                    return true;
                }
                if (FindState(sub.stateMachine, stateOrMachine, ref parentMachine, ref pos))
                    return true;
            }
            return false;
        }

        static int GetMachineLayer(AnimatorController ctrl, Object stateOrMachine, ref AnimatorStateMachine parentMachine, ref Vector3 pos)
        {
            var layers = ctrl.layers;
            pos = Vector3.zero;
            if (layers == null || layers.Length == 0)
                return -1;
            for (int i = 0; i < layers.Length; i++)
            {
                var l = layers[i];
                var m = l.stateMachine;
                if (FindState(m, stateOrMachine, ref parentMachine, ref pos))
                {
                    return i;
                }
            }
            return -1;
        }

        static bool HasParameter(AnimatorController anim, AnimatorControllerParameter apram)
        {
            foreach (var p in anim.parameters)
            {
                if (p.nameHash == apram.nameHash)
                    return true;
            }
            return false;
        }

        static void MergeParameters(AnimatorController from, AnimatorController to)
        {
            foreach (var arg in from.parameters)
            {
                if (!HasParameter(to, arg))
                {
                    to.AddParameter(arg);
                }
            }
        }

        //static AnimatorStateMachine AddSubStateMachine(AnimatorStateMachine m, AnimatorStateMachine subMachine, string name, Vector3 atPos,
        //    out AnimatorState defaultState)
        //{
        //    Dictionary<Object, Object> copyMap = new Dictionary<Object, Object>();
        //    defaultState = null;
        //    var result = CopyStatesRecursive(m, subMachine, name, atPos, copyMap, ref defaultState);
        //    return result;
        //}

        static AnimatorStateMachine CopyAsSubStateMachineRecursive(AnimatorStateMachine m, AnimatorStateMachine subMachine,
            string name, Vector3 atPos, Dictionary<Object, Object> copyMap, ref AnimatorState defaultState)
        {
            var sub = m.AddStateMachine(name, atPos);
            copyMap[subMachine] = sub;
            foreach (var st in subMachine.states)
            {
                var state = st.state;
                var subState = sub.AddState(state.name, st.position);
                if (state.motion is AnimationClip)
                {
                    subState.motion = state.motion;
                }
                else if (state.motion is BlendTree)
                {
                    subState.motion = Instantiate(state.motion);
                }
                copyMap[state] = subState;
                if (state == subMachine.defaultState)
                    defaultState = subState;
            }
            foreach (var m2 in subMachine.stateMachines)
            {
                CopyAsSubStateMachineRecursive(sub, m2.stateMachine, m2.stateMachine.name, m2.position, copyMap, ref defaultState);
            }
            CopyTransitionsRecursive(copyMap);
            return sub;
        }

        static AnimatorStateTransition CopyTransition(Dictionary<Object, Object> copyMap, AnimatorStateTransition trans)
        {
            AnimatorState destS = null;
            AnimatorStateMachine destM = null;
            if (trans.destinationState != null)
                destS = copyMap[trans.destinationState] as AnimatorState;
            else if (trans.destinationStateMachine != null)
                destM = copyMap[trans.destinationStateMachine] as AnimatorStateMachine;
            if (destM != null || destS != null)
            {
                var t = Instantiate(trans);
                t.name = trans.name;
                if (destS != null)
                    t.destinationState = destS;
                else
                    t.destinationStateMachine = destM;
                return t;
            }
            return null;
        }

        static void CopyTransitionsRecursive(Dictionary<Object, Object> copyMap)
        {
            foreach (var kv in copyMap)
            {
                var k = kv.Key;
                if (k is AnimatorState)
                {
                    var state = (AnimatorState)k;
                    foreach (var trans in state.transitions)
                    {
                        var t = CopyTransition(copyMap, trans);// Instantiate(trans);
                        //t.name = trans.name;
                        //if (trans.destinationState != null)
                        //    t.destinationState = copyMap[trans.destinationState] as AnimatorState;
                        //else if (trans.destinationStateMachine != null)
                        //    t.destinationStateMachine = copyMap[trans.destinationStateMachine] as AnimatorStateMachine;
                        if (t != null)
                            ((AnimatorState)kv.Value).AddTransition(t);
                    }
                }
                else if (k is AnimatorStateMachine)
                {
                    var m = (AnimatorStateMachine)k;
                    var m2 = (AnimatorStateMachine)kv.Value;
                    m2.entryPosition = m.entryPosition;
                    m2.exitPosition = m.exitPosition;
                    m2.anyStatePosition = m.anyStatePosition;
                }
            }
        }

        // 将和 baseState 关联的 transition 复制到与关联 copyiedMachine
        static void CopyTransitions(AnimatorStateMachine baseMachine, AnimatorState destState, AnimatorStateMachine destMachine,
            AnimatorStateMachine copiedMachine, AnimatorState defaultState)
        {
            CopyTransitionsTo(baseMachine, destState, destMachine, copiedMachine, defaultState);
            if (defaultState != null && destState != null)
            {
                foreach (var trans in destState.transitions)
                {
                    if (trans.isExit)
                        continue;
                    var t = Instantiate(trans);
                    t.name = trans.name;
                    defaultState.AddTransition(t);
                }
            }
        }

        static void CopyTransitionsTo(AnimatorStateMachine baseMachine, AnimatorState destState, AnimatorStateMachine destMachine,
            AnimatorStateMachine copiedMachine, AnimatorState defaultState)
        {
            if (baseMachine == destMachine)
                return;
            foreach (var st in baseMachine.states)
            {
                if (st.state == destState)
                    continue;
                foreach (var trans in st.state.transitions)
                {
                    if (destState != null && trans.destinationState == destState)
                    {
                        var t = Instantiate(trans);
                        t.name = trans.name;
                        if (defaultState != null)
                        {
                            t.destinationStateMachine = null;
                            t.destinationState = defaultState;
                        }
                        else
                        {
                            t.destinationStateMachine = copiedMachine;
                            t.destinationState = null;
                        }
                        st.state.AddTransition(t);
                    }
                    else if (destMachine != null && trans.destinationStateMachine == destMachine)
                    {
                        var t = Instantiate(trans);
                        t.name = trans.name;
                        t.destinationStateMachine = copiedMachine;
                        t.destinationState = null;
                        st.state.AddTransition(t);
                    }
                }
            }
            foreach (var m in baseMachine.stateMachines)
            {
                CopyTransitionsTo(m.stateMachine, destState, destMachine, copiedMachine, defaultState);
            }
        }

        [MenuItem("Game Toolkit/动画编辑/替换为子状态机", true)]
        static bool CanCopySubStateMachine()
        {
            AnimatorState baseState;
            AnimatorStateMachine baseMachine;
            AnimatorController copyMachine;
            return GetSelectedMachine(out baseState, out baseMachine, out copyMachine);
        }

        [MenuItem("Game Toolkit/动画编辑/替换为子状态机")]
        static void CopySubStateMachine()
        {
            AnimatorState baseState;
            AnimatorStateMachine baseMachine;
            AnimatorController copyMachine;
            var copy = GetSelectedMachine(out baseState, out baseMachine, out copyMachine);
            if (!copy)
                return;
            var path = AssetDatabase.GetAssetPath(baseState == null ? (Object)baseMachine : baseState);
            var ctrl = string.IsNullOrEmpty(path) ? null : AssetDatabase.LoadAssetAtPath<AnimatorController>(path);
            if (ctrl == null)
                return;
            int layer;
            Vector3 pos = Vector3.zero;
            AnimatorStateMachine machine = null;
            layer = GetMachineLayer(ctrl, baseState == null ? (Object)baseMachine : baseState, ref machine, ref pos);
            if (layer == -1 || copyMachine.layers.Length <= layer)
                return;
            AssetDatabase.StartAssetEditing();
            Undo.RegisterCompleteObjectUndo(ctrl, "Edit Animator");
            MergeParameters(copyMachine, ctrl);
            Dictionary<Object, Object> copyMap = new Dictionary<Object, Object>();
            AnimatorState defaultState = null;
            if (baseState != null)
            {
                var subMachine = CopyAsSubStateMachineRecursive(machine, copyMachine.layers[layer].stateMachine, baseState.name, pos,
                    copyMap, ref defaultState);
                CopyTransitions(ctrl.layers[layer].stateMachine, baseState, null, subMachine, defaultState);
                machine.RemoveState(baseState);
            }
            else
            {
                var subMachine = CopyAsSubStateMachineRecursive(machine, copyMachine.layers[layer].stateMachine, baseMachine.name, pos,
                    copyMap, ref defaultState);
                CopyTransitions(ctrl.layers[layer].stateMachine, null, baseMachine, subMachine, defaultState);
                machine.RemoveStateMachine(baseMachine);
            }
            EditorUtility.SetDirty(ctrl);
            AssetDatabase.StopAssetEditing();
        }

        public static void OpenCreater(string path)
        {
            var controller = AssetDatabase.LoadAssetAtPath<AnimCtrl>(path);
            if (controller == null)
                return;
            var win = GetWindow<CreateAnimatorWizzard>(true, "Create Animator", true);
            win.path = path;
            win.action = EAction.CreateClip;
            win.label.tooltip = path;
            win.label.text = controller.name;
            win.controller = controller;
            win.maxSize = new Vector2(450, 220);
            win.minSize = new Vector2(250, 220);
            win.ShowUtility();
        }

        public static void OpenRepath(string path)
        {
            var controller = AssetDatabase.LoadAssetAtPath<AnimCtrl>(path);
            if (controller == null)
                return;
            var win = GetWindow<CreateAnimatorWizzard>(true, "Repath Animator", true);
            win.path = path;
            win.folder = NativeFileSystem.GetFolderName(path);
            win.action = EAction.RepathClip;
            win.label.tooltip = path;
            win.label.text = controller.name;
            win.controller = controller;
            win.maxSize = new Vector2(720, 720);
            win.minSize = new Vector2(720, 220);
            win.texts.Clear();
            win.stateMachines.Clear();
            win.clipAssets.Clear();
            win.repathes.Clear();
            ClipAsset.GetClips(win.folder, win.clipAssets);
            var layers = controller.layers;
            for (int i = layers.Length - 1; i >= 0; i--)
            {
                win.stateMachines.Push(layers[i]);
            }
            win.ShowUtility();
        }

        EAction action;
        string clips = "";
        List<string> arr = new List<string>();
        string path = "";
        AnimCtrl controller;
        GUIContent label = new GUIContent();
        Vector2 scroll;

        StringBuilder texts = new StringBuilder();
        Stack<object> stateMachines = new Stack<object>();
        string folder;
        List<ClipAsset> clipAssets = new List<ClipAsset>();
        List<RepathClip> repathes = new List<RepathClip>();


        private void OnGUI()
        {
            if (controller == null)
            {
                Close();
                return;
            }
            if (action == EAction.CreateClip)
                CreateClipLayout();
            else if (action == EAction.RepathClip)
                RepathClipLayout();
        }

        void CreateClipLayout()
        {
            var rect = new Rect(10, 10, 230, 20);
            var ping = GUI.Button(rect, label, "ChannelStripAttenuationBar");
            if (ping)
                EditorGUIUtility.PingObject(controller);
            rect = new Rect(10, 30, 230, 20);
            EditorGUI.SelectableLabel(rect, "Animation Clips");
            rect = new Rect(10, 50, 230, 130);
            clips = DevilEditorUtility.TextArea(rect, clips, " List-out Animation Clips");
            rect = new Rect(40, 185, 80, 30);
            if (GUI.Button(rect, "Create"))
            {
                GUI.FocusControl(null);
                arr.Clear();
                if (string.IsNullOrEmpty(clips) || !StringUtil.ParseArray(clips, arr, '\n'))
                    return;
                if (controller == null || string.IsNullOrEmpty(path))
                {
                    EditorUtility.DisplayDialog("Error", "Can't find controller.", "OK");
                    return;
                }

                AssetDatabase.StartAssetEditing();
                var oldassets = AssetDatabase.LoadAllAssetsAtPath(path);
                foreach (var t in arr)
                {
                    if (string.IsNullOrEmpty(t))
                        continue;
                    var old = oldassets.Where((x) => x.name == t);
                    if (old != null && old.Count() > 0)
                        continue;
                    var clip = AnimCtrl.AllocateAnimatorClip(t);
                    AssetDatabase.AddObjectToAsset(clip, controller);
                }
                AssetDatabase.StopAssetEditing();
                AssetDatabase.ImportAsset(path);
                Close();
            }
            rect = new Rect(130, 185, 80, 30);
            if (GUI.Button(rect, "Delete"))
            {
                GUI.FocusControl(null);
                arr.Clear();
                if (string.IsNullOrEmpty(clips) || !StringUtil.ParseArray(clips, arr, '\n'))
                    return;
                if (controller == null || string.IsNullOrEmpty(path))
                {
                    EditorUtility.DisplayDialog("Error", "Can't find controller.", "OK");
                    return;
                }

                AssetDatabase.StartAssetEditing();
                var oldassets = AssetDatabase.LoadAllAssetsAtPath(path);
                foreach (var t in arr)
                {
                    if (string.IsNullOrEmpty(t))
                        continue;
                    var old = oldassets.Where((x) => x != null && x is AnimationClip && x.name == t);
                    if (old == null || old.Count() == 0)
                        continue;
                    var first = old.First();
                    if (first == null)
                        continue;
                    Object.DestroyImmediate(first, true);
                }
                AssetDatabase.StopAssetEditing();

                AssetDatabase.ImportAsset(path);

                Close();
            }
        }

        void RepathClipLayout()
        {
            if (stateMachines.Count > 0)
            {
                var mc = stateMachines.Pop();
                if (mc is AnimLayer)
                {
                    texts.Append(((AnimLayer)mc).name).Append('\n');
                    stateMachines.Push(((AnimLayer)mc).stateMachine);
                }
                else
                {
                    AnimSM machine = (AnimSM)mc;
                    foreach (var stat in machine.states)
                    {
                        var repath = new RepathClip(machine, stat.state, this);
                        repathes.Add(repath);
                        AppendRepath(repath);
                    }
                    var submc = machine.stateMachines;
                    foreach (var child in submc)
                    {
                        stateMachines.Push(child.stateMachine);
                    }
                }
                label.text = texts.ToString();
                this.Repaint();
            }
            scroll = GUILayout.BeginScrollView(scroll);
            EditorGUILayout.LabelField(label, "textarea");
            GUILayout.EndScrollView();
        }

        void AppendRepath(RepathClip repath)
        {
            if (repath.asset != null)
            {
                texts.AppendTab(1).Append(repath.src).Append(" -> ").Append(repath.asset.dest).Append('\n');
            }
            else if (repath.children != null)
            {
                foreach (var it in repath.children)
                {
                    AppendRepath(it);
                }
            }
            else
            {
                texts.AppendTab(1).Append(repath.src).Append(" -> __").Append('\n');
            }
        }

        class ClipAsset
        {
            public string path;
            public string dest;
            public AnimationClip clip;

            public static void GetClips(string folder, ICollection<ClipAsset> assets)
            {
                HashSet<string> exists = new HashSet<string>();
                var guids = AssetDatabase.FindAssets("t:animationclip", new string[] { folder });
                foreach (var id in guids)
                {
                    var p = AssetDatabase.GUIDToAssetPath(id);
                    if (exists.Add(p))
                    {
                        var clips = AssetDatabase.LoadAllAssetsAtPath(p);
                        foreach (var c in clips)
                        {
                            if (c is AnimationClip)
                            {
                                ClipAsset ca = new ClipAsset();
                                ca.path = p;
                                ca.dest = ParallelUtils.Concat(NativeFileSystem.GetFileName(p, false), '/', c.name);
                                ca.clip = (AnimationClip)c;
                                assets.Add(ca);
                            }
                        }
                    }
                }
            }
        }

        class RepathClip
        {
            public string src;
            public AnimState state;
            public ClipAsset asset;
            public RepathClip[] children;

            public RepathClip(AnimSM machine, AnimState state, CreateAnimatorWizzard win)
            {
                this.state = state;
                if (state.motion is AnimationClip)
                {
                    var clip = (AnimationClip)state.motion;
                    var path = AssetDatabase.GetAssetPath(clip);
                    if (!string.IsNullOrEmpty(path))
                    {
                        src = NativeFileSystem.GetFileName(path, false);
                        var n = path.LastIndexOf('@');
                        string subfix = n == -1 ? src : path.Substring(n);
                        src = ParallelUtils.Concat(src, '/', clip.name);
                        foreach (var ca in win.clipAssets)
                        {
                            if (ca.clip.name == clip.name && ca.path.EndsWithIgnoreCase(subfix))
                            {
                                asset = ca;
                                break;
                            }
                        }
                    }
                    else
                    {
                        src = clip.name;
                    }
                }
                else if (state.motion is AnimBlend)
                {
                    var blends = ((AnimBlend)state.motion).children;
                    children = new RepathClip[blends.Length];
                    for (int i = 0; i < blends.Length; i++)
                    {
                        children[i] = new RepathClip(machine, this, i, blends[i], win);
                    }
                }
                else
                {
                    src = string.Format("(invaild) {0}/{1}", machine.name, state.name);
                }
            }

            public RepathClip(AnimSM machine, RepathClip parent, int index, ChildMotion cmotion, CreateAnimatorWizzard win)
            {
                //this.state = parent.state;
                if (cmotion.motion is AnimationClip)
                {
                    var clip = (AnimationClip)cmotion.motion;
                    var path = AssetDatabase.GetAssetPath(clip);
                    if (!string.IsNullOrEmpty(path))
                    {
                        src = NativeFileSystem.GetFileName(path, false);
                        var n = path.LastIndexOf('@');
                        string subfix = n == -1 ? src : path.Substring(n);
                        src = ParallelUtils.Concat(src, '/', clip.name);
                        foreach (var ca in win.clipAssets)
                        {
                            if (ca.clip.name == clip.name && ca.path.EndsWithIgnoreCase(subfix))
                            {
                                asset = ca;
                                break;
                            }
                        }
                    }
                    else
                    {
                        src = ParallelUtils.Concat(parent.src, '/', clip.name);
                    }
                }
                else if (cmotion.motion is AnimBlend)
                {
                    var blends = ((AnimBlend)cmotion.motion).children;
                    children = new RepathClip[blends.Length];
                    for (int i = 0; i < blends.Length; i++)
                    {
                        children[i] = new RepathClip(machine, this, i, blends[i], win);
                    }
                }
                else
                {
                    src = string.Format("(invaild) {0}/{1}[{2}", machine.name, parent.src, index);
                }
            }
        }
    }
}